/*!
   \file

   \brief
      Wrapper for Windows OS window.

   © 2011 Mark W. Gabby II

   This file is part of Rock Hunter.

   Rock Hunter is free software: you can redistribute it and/or modify it under the terms of the
   GNU General Public License as published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   Rock Hunter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
   even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along with Rock Hunter.
   If not, see <http://www.gnu.org/licenses/>.
*/
#include "WindowsWindow.hpp"

#ifndef WINVER
#define WINVER 0x0501 // Windows XP
#endif
#include <windowsx.h> // For GET_ param macros

#include "Systems/Platform/Platform.hpp"

#include "Math/Math.hpp"

#include "PlatformTypesWin.hpp"

namespace pf
{
extern tSendInputMessage gSendInputMessageCallback;
extern tSetModifierState gSetModifierStateCallback;
extern tControllerCaptured gControllerCapturedCallback;
extern tControllerReleased gControllerReleasedCallback;

extern tAddGraphicsViewport gAddGraphicsViewportCallback;
}

extern HINSTANCE gInstanceHandle;
static tWindowID gNextID = 0;

/*!
   \brief
      Window procedure for this window. Handles input processing and manages the mouse.
*/
LRESULT CALLBACK WindowsWindowProc( HWND WindowHandle, UINT Msg, WPARAM WParam, LPARAM LParam )
{
   std::map<HWND, WindowsWindow *>::iterator WindowIt = gPFData.HandlesToWindows.find( WindowHandle );
   WindowsWindow * WindowInstance = NULL;
   if ( WindowIt != gPFData.HandlesToWindows.end() )
   {
      WindowInstance = ( *WindowIt ).second;
   }

   if ( WindowInstance == NULL )
   {
      return DefWindowProc( WindowHandle, Msg, WParam, LParam );
   }

   if ( WindowInstance->ControllerCaptured() )
   {
      // Send controller status updates using the query interface
      JOYINFOEX ControllerInfo;
      ControllerInfo.dwSize = sizeof( ControllerInfo );
      ControllerInfo.dwFlags = JOY_RETURNALL;
      if ( joyGetPosEx( WindowInstance->GetControllerIndex(), &ControllerInfo ) == JOYERR_NOERROR )
      {
         WindowInstance->SendInputControllerUpdates( ControllerInfo );
      }
   }

   switch ( Msg )
   {
   case WM_MOUSELEAVE:
      WindowInstance->ShowMouse();
      break;

   case WM_MOUSEMOVE:
   {
      WindowInstance->SendInputMouseMoveMessage( Msg, WParam, LParam );

      // Possibly release the mouse if the buttons aren't being held anymore
      WindowInstance->MaybeReleaseMouse( -1 );

      WindowInstance->UpdateMouseVisibility( LOWORD( LParam ), HIWORD( LParam ) );

   }
   break;

   case WM_MOUSEWHEEL:
      WindowInstance->SendInputMouseWheelMessage( Msg, WParam, LParam );
      break;
//    case WM_NCXBUTTONDOWN:
//    case WM_NCXBUTTONDBLCLK:
//    case WM_NCXBUTTONUP:
      break;

      // Mouse button down messages. Set that button as captured and send the message
   case WM_LBUTTONDOWN:
      WindowInstance->MaybeCaptureMouse( 0 );
      // Fall through
   case WM_LBUTTONDBLCLK:
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_RBUTTONDOWN:
      WindowInstance->MaybeCaptureMouse( 1 );
      // Fall through
   case WM_RBUTTONDBLCLK:
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_MBUTTONDOWN:
      WindowInstance->MaybeCaptureMouse( 2 );
      // Fall through
   case WM_MBUTTONDBLCLK:
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_XBUTTONDOWN:
   {

      // Here, we have to check which button it was before we can set it as
      // captured.
      int8_t ButtonIndex = 3; // MK_XBUTTON1

      if ( HIWORD( WParam ) == MK_XBUTTON2 )
      {
         ButtonIndex = 4; // MK_XBUTTON2
      }

      WindowInstance->MaybeCaptureMouse( ButtonIndex );
   }
   // Fall through
   case WM_XBUTTONDBLCLK:
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

      // Mouse button up messages. Release the button, then release the mouse if
      // no other buttons are captured.
   case WM_LBUTTONUP:
      // A mouse button went up. We may want to release mouse capture
      // button index = 0
      WindowInstance->MaybeReleaseMouse( 0 );
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_RBUTTONUP:
      // A mouse button went up. We may want to release mouse capture
      // button index = 1
      WindowInstance->MaybeReleaseMouse( 1 );
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_MBUTTONUP:
      // A mouse button went up. We may want to release mouse capture
      // button index = 2
      WindowInstance->MaybeReleaseMouse( 2 );
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
      break;

   case WM_XBUTTONUP:
   {
      int8_t ButtonIndex = 3; // MK_XBUTTON1

      if ( HIWORD( WParam ) == MK_XBUTTON2 )
      {
         ButtonIndex = 4; // MK_XBUTTON2
      }

      // A mouse button went up. We may want to release mouse capture
      WindowInstance->MaybeReleaseMouse( ButtonIndex );
      WindowInstance->SendInputMouseButtonMessage( Msg, WParam, LParam );
   }
   break;

   // Keyboard Key messages
   case WM_KEYDOWN:
   case WM_KEYUP:
      WindowInstance->SendInputKeyboardKeyMessage( Msg, WParam, LParam );
      return 0;

      // Character input messages
   case WM_CHAR:
      WindowInstance->SendInputKeyboardKeyValueMessage( Msg, WParam, LParam );
      return 0;

   case WM_CREATE:
      return 0;

   case WM_PAINT:
      // Messageboxes weren't showing up unless I did this
      return DefWindowProc( WindowHandle, Msg, WParam, LParam );

   case WM_DESTROY:
      return 0;

   case WM_MOVING:
      //WindowInstance->Moving();
      return 0;

      //case WM_ENTERSIZEMOVE:   // For fancy resize-to-resolution features, yo.
      // return 0;
      //case WM_EXITSIZEMOVE:
      // return 0;
   case WM_SYSCOMMAND:
      switch ( WParam )
      {
      case SC_CLOSE:
         PostQuitMessage( 0 );
         return 0;
      case SC_MAXIMIZE:
         return DefWindowProc( WindowHandle, Msg, WParam, LParam );
      case SC_MINIMIZE:
         return DefWindowProc( WindowHandle, Msg, WParam, LParam );
      default:
         return DefWindowProc( WindowHandle, Msg, WParam, LParam );
      }
   default:
      return DefWindowProc( WindowHandle, Msg, WParam, LParam );
   }
   return 0;
}


/*!
   \brief
      Gets a normalized controller amount, from -1.0f to 1.0f.

   \param Position
      Position of the controller on this axis.
   \param Min
      Minimum position on this axis.
   \param Max
      Maximum position on this axis.

   \return
      A number from -1 to 1 indicating the position along the axis of this controller.
*/
float GetControllerAmount( const int32_t Position, const uint16_t Min, const uint16_t Max )
{
   // First, handle out-of-range values
   if ( Position < Min )
   {
      return -1.0f;
   }
   else if ( Position > Max )
   {
      return 1.0f;
   }

   // Get the total distance from min to the position
   const int32_t PositionDistanceFromMin = Position - Min;
   // Get the total distance from min to max
   const int32_t Delta = Max - Min;

   // Divide to get the percentage along the distance out axis is at
   const float PercentAlongDistance = ( float )PositionDistanceFromMin / ( float )Delta;

   // Map this value from -1.0f to 1.0f.
   return ( PercentAlongDistance * 2.0f ) - 1.0f;
}

WindowsWindow::WindowsWindow()
   : openGLContextHandle( NULL ), windowHandle( NULL ), capturedControllerIndex( kNull8 ),
     joySetCaptureCalled( false ), borderWidthX( 0 ), borderWidthY( 0 ), captionWidth( 0 ), input(),
     id( gNextID )
{
   ++gNextID;

   memset( ( void * )&previousControllerStatus, 0, sizeof( previousControllerStatus ) );
}

bool WindowsWindow::Initialize( const bool Border, tPixelFormatIndex PixelFormatIndex,
                                const uint16_t WidthPixels, const uint16_t HeightPixels,
                                const int16_t PixelPositionX, const int16_t PixelPositionY,
                                const float CustomFOV, const float CustomAspectRatio,
                                const char * const Name, const char * const Caption )
{
   // Set up a normal set of parameters.
   WNDCLASS WindowClass;
   WindowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
   WindowClass.lpfnWndProc = WindowsWindowProc;
   WindowClass.cbClsExtra = 0;
   WindowClass.cbWndExtra = 0;
   WindowClass.hInstance = gInstanceHandle;
   WindowClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
   WindowClass.hCursor = LoadCursor( NULL, IDC_ARROW );
   WindowClass.hbrBackground = ( HBRUSH )GetStockObject( BLACK_BRUSH );
   WindowClass.lpszMenuName = NULL;
   WindowClass.lpszClassName = Name;

   DWORD WindowStyle;
   WindowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

   // Add extra width so that the drawable area is equal to the requested resolution
   borderWidthX = GetSystemMetrics( SM_CYFIXEDFRAME );
   borderWidthY = GetSystemMetrics( SM_CYFIXEDFRAME );
   captionWidth = GetSystemMetrics( SM_CYCAPTION );
   uint32_t ExtraWindowWidth = borderWidthX * 2;
   uint32_t ExtraWindowHeight = borderWidthY * 2 + captionWidth;

   uint16_t WidthPixelsToUse = WidthPixels ? WidthPixels : 320;
   uint16_t HeightPixelsToUse = HeightPixels ? HeightPixels : 200;

   const int16_t PixelPositionToUseX = PixelPositionX ? PixelPositionX : 0;
   const int16_t PixelPositionToUseY = PixelPositionY ? PixelPositionY : 0;

   // Change the parameters depending on the specific style.
   uint16_t WidthBufferPixels = 0;
   uint16_t HeightBufferPixels = 0;
   if ( Border )
   {
      WindowStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
                    WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE;
      WidthBufferPixels = ExtraWindowWidth;
      HeightBufferPixels = ExtraWindowHeight;
      WindowClass.hbrBackground = ( HBRUSH )GetStockObject( BLACK_BRUSH );
   }
   else
   {
      WindowClass.hbrBackground = ( HBRUSH )GetStockObject( BLACK_BRUSH );
      WindowStyle = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE;
   }

   // Register the window class.
   RegisterClass( &WindowClass );

   // Create the window
   windowHandle = CreateWindow( Name, Caption, WindowStyle,
                                PixelPositionToUseX, PixelPositionToUseY,
                                WidthPixelsToUse + WidthBufferPixels,
                                HeightPixelsToUse + HeightBufferPixels,
                                NULL, NULL, gInstanceHandle, NULL );

   // Get the handle to the Device Context for later use.
   HDC DeviceContext = GetDC( windowHandle );

   // Create a simple pixel format desired.
   PIXELFORMATDESCRIPTOR pfd = { 0 };
   pfd.nSize = sizeof( pfd );
   pfd.nVersion = 1;

   const tSystemPixelFormatIndex SystemPixelFormatIndex = gPFData.SupportedPixelFormatsToSystemPixelFormats[PixelFormatIndex];

   ::SetPixelFormat( DeviceContext, SystemPixelFormatIndex, &pfd );

   // Create an OpenGL context for this window, since this is a game engine
   openGLContextHandle = wglCreateContext( DeviceContext );

   pf::SetActiveContext( GetID() );

   pf::gAddGraphicsViewportCallback( GetID(), WidthPixelsToUse, HeightPixelsToUse,
                                     CustomFOV, CustomAspectRatio );

   ReleaseDC( windowHandle, DeviceContext );

   gPFData.HandlesToWindows[windowHandle] = this;

   // TODO: Error checking would be nice...
   return true;
}

void WindowsWindow::UpdateMouseVisibility( const int16_t PosX, const int16_t PosY )
{
   if ( ShouldHideMouse( PosX, PosY ) )
   {
      HideMouse();
   }
   else
   {
      ShowMouse();
   }
}

bool WindowsWindow::AllMouseButtonsReleased()
{
   for ( int ButtonIndex = 0; ButtonIndex < kMaxMouseButtons; ++ButtonIndex )
   {
      if ( input.mouseButtonCaptured[ButtonIndex] == true )
      {
         return false;
      }
   }
   return true;
}

bool WindowsWindow::MouseCaptured()
{
   return input.mouseCaptured;
}

bool WindowsWindow::ShouldReleaseMouse()
{
   // If we're not capturing a mouse button:
   if ( AllMouseButtonsReleased() && input.mouseCaptured )
   {
      return true;
   }

   return false;
}

bool WindowsWindow::ShouldHideMouse( const int16_t PosX, const int16_t PosY )
{
   if ( MouseInWindow( PosX, PosY ) )
   {
      return true;
   }
   return false;
}

void WindowsWindow::HideMouse()
{
   if ( input.mouseHidden == false )
   {
      ShowCursor( FALSE );
      input.mouseHidden = true;
   }
}

void WindowsWindow::ShowMouse()
{
   if ( input.mouseHidden )
   {
      ShowCursor( TRUE );
      input.mouseHidden = false;
   }
}

void WindowsWindow::MaybeReleaseMouse( int8_t ButtonIndex )
{
   if ( ButtonIndex >= 0 )
   {
      input.mouseButtonCaptured[ButtonIndex] = false;
   }

   if ( ShouldReleaseMouse() )
   {
      ReleaseMouse();
      input.mouseCaptured = false;
   }
}

void WindowsWindow::MaybeCaptureMouse( int8_t ButtonIndex )
{
   if ( ButtonIndex >= 0 )
   {
      input.mouseButtonCaptured[ButtonIndex] = true;
   }

   // Capture the mouse
   if ( MouseCaptured() == false )
   {
      CaptureMouse();
      input.mouseCaptured = true;
   }
}

void WindowsWindow::CaptureMouse()
{
   SetCapture( windowHandle );
}
void WindowsWindow::ReleaseMouse()
{
   SetCapture( NULL );
}

void WindowsWindow::SendInputKeyboardKeyMessage( UINT Msg, WPARAM WParam, LPARAM LParam )
{
   KeyMessage Key = { KK_None, KS_None };
   KeyInputMessage KeyInput = { KI_None, false };

   uint32_t RepeatCount = LParam & 0xFFFF;
   int32_t OEMscan = 0x00FF & HIWORD( LParam );
   bool IsExtendedKey =  ( 1 << 24 ) & LParam;

   uint32_t VKcode = MapVirtualKeyEx( OEMscan, 3, NULL );

   switch ( Msg )
   {
   case WM_KEYDOWN:
      Key.state = KS_Down;
      break;

   case WM_KEYUP:
      Key.state = KS_Up;
      break;
   }

   // First, look at the keys that have VK_ constants assigned to them.
   // This includes most keys excluding the number and letter keys.
   switch ( WParam )
   {
      // Backspace
   case VK_BACK:
      Key.which = KK_BackSpace;
      KeyInput.value = KI_Backspace;
      break;

   case VK_TAB:
      Key.which = KK_Tab;
      KeyInput.value = KI_Tab;
      break;

   case VK_RETURN:
      KeyInput.value = KI_Enter;

      if ( IsExtendedKey )
      {
         Key.which = KK_NumEnter;
      }
      else
      {
         Key.which = KK_Return;
      }

      break;

      // It's a shift message ...
   case VK_SHIFT:
   {
      KeyInput.value = KI_Shift;

      // Use the expanded information to figure out
      // what shift it was.
      switch ( VKcode )
      {
      case VK_LSHIFT:
         Key.which = KK_LeftShift;
         break;

      case VK_RSHIFT:
         Key.which = KK_RightShift;
         break;

      default:
         break;
      }
   }
   break;

   case VK_CONTROL:
   {
      KeyInput.value = KI_Ctrl;

      if ( IsExtendedKey )
      {
         Key.which = KK_RightCtrl;
      }
      else
      {
         Key.which = KK_LeftCtrl;
      }
   }
   break;

   // Pause-break
   case VK_PAUSE:
      Key.which = KK_PauseBreak;
      KeyInput.value = KI_PauseBreak;
      break;

      // Capslock
   case VK_CAPITAL:
   {
      Key.which = KK_CapsLock;
      KeyInput.value = KI_CapsLock;
   }
   break;

   case VK_ESCAPE:
      Key.which = KK_Escape;
      KeyInput.value = KI_Escape;
      break;

   case VK_SPACE:
      Key.which = KK_Space;
      break;

      // Page Up
   case VK_PRIOR:
      KeyInput.value = KI_PageUp;

      if ( IsExtendedKey )
      {
         Key.which = KK_PageUp;
      }
      else
      {
         Key.which = KK_Num9;
      }

      break;

      // Page Down
   case VK_NEXT:
      KeyInput.value = KI_PageDown;

      if ( IsExtendedKey )
      {
         Key.which = KK_PageDown;
      }
      else
      {
         Key.which = KK_Num3;
      }

      break;

   case VK_END:
      KeyInput.value = KI_End;

      if ( IsExtendedKey )
      {
         Key.which = KK_End;
      }
      else
      {
         Key.which = KK_Num1;
      }

      break;

   case VK_HOME:
      KeyInput.value = KI_Home;

      if ( IsExtendedKey )
      {
         Key.which = KK_Home;
      }
      else
      {
         Key.which = KK_Num7;
      }

      break;

      // Arrow keys
   case VK_LEFT:
      KeyInput.value = KI_Left;

      if ( IsExtendedKey )
      {
         Key.which = KK_Left;
      }
      else
      {
         Key.which = KK_Num4;
      }

      break;

   case VK_UP:
      KeyInput.value = KI_Up;

      if ( IsExtendedKey )
      {
         Key.which = KK_Up;
      }
      else
      {
         Key.which = KK_Num8;
      }

      break;

   case VK_RIGHT:
      KeyInput.value = KI_Right;

      if ( IsExtendedKey )
      {
         Key.which = KK_Right;
      }
      else
      {
         Key.which = KK_Num6;
      }

      break;

   case VK_DOWN:
      KeyInput.value = KI_Down;

      if ( IsExtendedKey )
      {
         Key.which = KK_Down;
      }
      else
      {
         Key.which = KK_Num2;
      }

      break;

      // Printscreen Key
   case VK_SNAPSHOT:
      Key.which = KK_PrintScreen;
      KeyInput.value = KI_PrintScreen;
      break;

   case VK_INSERT:
      KeyInput.value = KI_Insert;

      if ( IsExtendedKey )
      {
         Key.which = KK_Insert;
      }
      else
      {
         Key.which = KK_Num0;
      }

      break;

   case VK_DELETE:
      KeyInput.value = KI_Delete;

      if ( IsExtendedKey )
      {
         Key.which = KK_Delete;
      }
      else
      {
         Key.which = KK_NumPeriod;
      }

      break;

   case VK_LWIN:
      Key.which = KK_LeftWindows;
      KeyInput.value = KI_Windows;
      break;

   case VK_RWIN:
      Key.which = KK_RightWindows;
      KeyInput.value = KI_Windows;
      break;

   case VK_APPS:
      Key.which = KK_ContextMenu;
      KeyInput.value = KI_Context;
      break;

   case VK_CLEAR:
      Key.which = KK_Num5;
      KeyInput.value = KI_Clear;
      break;

   case VK_NUMPAD0:
      Key.which = KK_Num0;
      break;

   case VK_NUMPAD1:
      Key.which = KK_Num1;
      break;

   case VK_NUMPAD2:
      Key.which = KK_Num2;
      break;

   case VK_NUMPAD3:
      Key.which = KK_Num3;
      break;

   case VK_NUMPAD4:
      Key.which = KK_Num4;
      break;

   case VK_NUMPAD5:
      Key.which = KK_Num5;
      break;

   case VK_NUMPAD6:
      Key.which = KK_Num6;
      break;

   case VK_NUMPAD7:
      Key.which = KK_Num7;
      break;

   case VK_NUMPAD8:
      Key.which = KK_Num8;
      break;

   case VK_NUMPAD9:
      Key.which = KK_Num9;
      break;

   case VK_NUMLOCK:
      Key.which = KK_NumLock;
      KeyInput.value = KI_NumLock;
      break;

   case VK_MULTIPLY:
      Key.which = KK_Asterisk;
      break;

   case VK_ADD:
      Key.which = KK_NumPlus;
      break;

   case VK_SUBTRACT:
      Key.which = KK_Minus;
      break;

   case VK_DECIMAL:
      Key.which = KK_NumPeriod;
      break;

   case VK_DIVIDE:
      Key.which = KK_NumForwardSlash;
      break;

   case VK_F1:
      Key.which = KK_F1;
      KeyInput.value = KI_F1;
      break;

   case VK_F2:
      Key.which = KK_F2;
      KeyInput.value = KI_F2;
      break;

   case VK_F3:
      Key.which = KK_F3;
      KeyInput.value = KI_F3;
      break;

   case VK_F4:
      Key.which = KK_F4;
      KeyInput.value = KI_F4;
      break;

   case VK_F5:
      Key.which = KK_F5;
      KeyInput.value = KI_F5;
      break;

   case VK_F6:
      Key.which = KK_F6;
      KeyInput.value = KI_F6;
      break;

   case VK_F7:
      Key.which = KK_F7;
      KeyInput.value = KI_F7;
      break;

   case VK_F8:
      Key.which = KK_F8;
      KeyInput.value = KI_F8;
      break;

   case VK_F9:
      Key.which = KK_F9;
      KeyInput.value = KI_F9;
      break;

   case VK_F10:
      Key.which = KK_F10;
      KeyInput.value = KI_F10;
      break;

   case VK_F11:
      Key.which = KK_F11;
      KeyInput.value = KI_F11;
      break;

   case VK_F12:
      Key.which = KK_F12;
      KeyInput.value = KI_F12;
      break;

   case VK_SCROLL:
      Key.which = KK_ScrollLock;
      KeyInput.value = KI_ScrollLock;
      break;

   case VK_OEM_1:
      Key.which = KK_Colon;
      break;

   case VK_OEM_PLUS:
      Key.which = KK_Equals;
      break;

   case VK_OEM_COMMA:
      Key.which = KK_Comma;
      break;

   case VK_OEM_MINUS:
      Key.which = KK_Dash;
      break;

   case VK_OEM_PERIOD:
      Key.which = KK_Period;
      break;

   case VK_OEM_2:
      Key.which = KK_Slash;
      break;

   case VK_OEM_3:
      Key.which = KK_Tilde;
      break;

   case VK_OEM_4:
      Key.which = KK_LeftBracket;
      break;

   case VK_OEM_5:
      Key.which = KK_BackSlash;
      break;

   case VK_OEM_6:
      Key.which = KK_RightBracket;
      break;

   case VK_OEM_7:
      Key.which = KK_Quote;

   default:
      break;
   }

   // If we didn't find the key yet, check for valid letter and number keys.
   if ( Key.which == KK_None )
   {
      if ( WParam >= '0' && WParam <= '9' )
      {
         Key.which = ( eKeyboardKey )( ( uint32_t )KK_0 + ( WParam - '0' ) );
      }
      else if ( WParam >= 'A' && WParam <= 'Z' )
      {
         Key.which = ( eKeyboardKey )( ( uint32_t )KK_A + ( WParam - 'A' ) );
      }
   }

   // If we found a message, do some final work on it
   if ( Key.which != KK_None )
   {
      // Finally, send the message!
      // The key button message: Which button was pressed on the keyboard?
      InputMessage Message;
      Message.type = kKeyboardKey;
      Message.keyboardKey = Key;

      // Send messages equal to the repeat count.
      pf::gSendInputMessageCallback( Message );

      // The key value message: What was the value of the key?
      // For example, if [A] was pressed, the value might be 'a' or 'A', depending on caps lock and
      // shift states.
      InputMessage KeyInputMessage;
      KeyInputMessage.type = kKeyInputValue;
      KeyInputMessage.keyInput = KeyInput;

      // Don't bother sending the message if the value is KI_None.
      if ( KeyInputMessage.keyInput.value != KI_None )
      {
          pf::gSendInputMessageCallback( KeyInputMessage );
      }

      for ( uint32_t i = 0; i < RepeatCount - 1; ++i )
      {
          pf::gSendInputMessageCallback( Message );
          if ( KeyInputMessage.keyInput.value != KI_None )
          {
               pf::gSendInputMessageCallback( KeyInputMessage );
          }
      }
   }

   // Send the state of the various modifier keys to Input.
   bool CtrlIsDown = HIWORD( GetKeyState( VK_CONTROL ) );
   bool ShiftIsDown = HIWORD( GetKeyState( VK_SHIFT ) );

   bool NumLockIsOn = LOWORD( GetKeyState( VK_NUMLOCK ) );
   bool CapsLockIsOn = LOWORD( GetKeyState( VK_CAPITAL ) );
   bool ScrollLockIsOn = LOWORD( GetKeyState( VK_SCROLL ) );

   pf::gSetModifierStateCallback( KI_Ctrl, CtrlIsDown );
   pf::gSetModifierStateCallback( KI_Shift, ShiftIsDown );

   pf::gSetModifierStateCallback( KI_NumLock, NumLockIsOn );
   pf::gSetModifierStateCallback( KI_CapsLock, CapsLockIsOn );
   pf::gSetModifierStateCallback( KI_ScrollLock, ScrollLockIsOn );
}

bool ValueIsASCIIprintable( uint8_t Value )
{
   if ( Value >= kMinASCIIprintable &&
         Value <= kMaxASCIIprintable )
   {
      return true;
   }

   return false;
}

void WindowsWindow::SendInputKeyboardKeyValueMessage( UINT Msg, WPARAM WParam, LPARAM LParam )
{
   InputMessage KeyInputMessage;

   KeyInputMessage.keyInput.printable = false;

   if ( ValueIsASCIIprintable( WParam ) )
   {
      KeyInputMessage.keyInput.printable = true;
   }

   KeyInputMessage.type = kKeyInputValue;
   KeyInputMessage.keyInput.value = ( eKeyInput )WParam;

   pf::gSendInputMessageCallback( KeyInputMessage );
}

void WindowsWindow::SendInputMouseButtonMessage( UINT Msg, WPARAM WParam, LPARAM LParam )
{
   InputMessage Message;
   Message.type = kMouseButton;
   MouseButtonMessage ToSend;

   ToSend.xPos = GET_X_LPARAM( LParam );
   ToSend.yPos = GET_Y_LPARAM( LParam );
   ToSend.isDoubleClick = false;
   ToSend.state = KS_Down;

   switch ( Msg )
   {
   case WM_LBUTTONDOWN:
      ToSend.which = kMouseL;
      break;

   case WM_LBUTTONUP:
      ToSend.which = kMouseL;
      ToSend.state = KS_Up;
      break;

   case WM_LBUTTONDBLCLK:
      ToSend.which = kMouseL;
      ToSend.isDoubleClick = true;
      break;

   case WM_RBUTTONDOWN:
      ToSend.which = kMouseR;
      break;

   case WM_RBUTTONUP:
      ToSend.which = kMouseR;
      ToSend.state = KS_Up;
      break;

   case WM_RBUTTONDBLCLK:
      ToSend.which = kMouseR;
      ToSend.isDoubleClick = true;
      break;

   case WM_MBUTTONDOWN:
      ToSend.which = kMouseM;
      break;

   case WM_MBUTTONUP:
      ToSend.which = kMouseM;
      ToSend.state = KS_Up;
      break;

   case WM_MBUTTONDBLCLK:
      ToSend.which = kMouseM;
      ToSend.isDoubleClick = true;
      break;

   case WM_XBUTTONDOWN:

      // Check which one this is specifically
      if ( HIWORD( WParam ) == MK_XBUTTON1 )
      {
         ToSend.which = kMouse4;
      }
      else // XBUTTON2
      {
         ToSend.which = kMouse5;
      }

      break;

   case WM_XBUTTONUP:

      // Check which one this is specifically
      if ( HIWORD( WParam ) == MK_XBUTTON1 )
      {
         ToSend.which = kMouse4;
      }
      else // XBUTTON2
      {
         ToSend.which = kMouse5;
      }

      ToSend.state = KS_Up;
      break;

   case WM_XBUTTONDBLCLK:

      // Check which one this is specifically
      if ( HIWORD( WParam ) == MK_XBUTTON1 )
      {
         ToSend.which = kMouse4;
      }
      else // XBUTTON2
      {
         ToSend.which = kMouse5;
      }

      ToSend.isDoubleClick = true;
      break;

      //    case WM_NCXBUTTONDOWN:
      //        break;
      //    case WM_NCXBUTTONUP:
      //        break;
      //    case WM_NCXBUTTONDBLCLK:
      //        break;
   default:
      break;
   }

   Message.mouseBtn = ToSend;

   pf::gSendInputMessageCallback( Message );
}

void WindowsWindow::SendInputMouseMoveMessage( UINT Msg, WPARAM WParam, LPARAM LParam )
{
   InputMessage Message;
   Message.type = kMouseMove;
   MouseMoveMessage ToSend;
   ToSend.xPos = GET_X_LPARAM( LParam );
   ToSend.yPos = GET_Y_LPARAM( LParam );
   Message.mouseMove = ToSend;

   pf::gSendInputMessageCallback( Message );
}

void WindowsWindow::SendInputMouseWheelMessage( UINT Msg, WPARAM WParam, LPARAM LParam )
{
   InputMessage Message;
   Message.type = kMouseWheel;
   MouseWheelMessage ToSend;
   ToSend.xPos = GET_X_LPARAM( LParam );
   ToSend.yPos = GET_Y_LPARAM( LParam );

   ToSend.which = kHorizontal;
   ToSend.units = GET_WHEEL_DELTA_WPARAM( WParam ) / ( float )WHEEL_DELTA;

   Message.mouseWheel = ToSend;

   pf::gSendInputMessageCallback( Message );
}

void WindowsWindow::SendInputControllerUpdates( JOYINFOEX & ControllerStatus )
{
   // Compare buttons - if there are any differences, we need to send updates
   if ( ( ControllerStatus.dwButtons ^ previousControllerStatus.dwButtons ) != 0 )
   {
      // For each button (the last shift will result in a zero)
      for ( uint32_t ButtonFlag = 1; ButtonFlag > 0; ButtonFlag <<= 1 )
      {
         bool ButtonWasDown = previousControllerStatus.dwButtons & ButtonFlag;
         bool ButtonIsDown = ControllerStatus.dwButtons & ButtonFlag;

         // If this button's state has changed
         if ( ButtonWasDown != ButtonIsDown )
         {
            // If the button is currently down, send a "button pressed down" message
            if ( ButtonIsDown )
            {
               SendInputControllerButtonMessage( KS_Down, ButtonFlag );
            }
            // If the button is currently up, send a "button pressed up" message
            else
            {
               SendInputControllerButtonMessage( KS_Up, ButtonFlag );
            }
         }
      }
   }

   // Controller axis messages
   if ( ControllerStatus.dwXpos != previousControllerStatus.dwXpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwXpos,
                           controllerCaps.wXmin, controllerCaps.wXmax );

      SendInputControllerAxisMessage( Amount, kAxisX );
   }
   if ( ControllerStatus.dwYpos != previousControllerStatus.dwYpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwYpos,
                           controllerCaps.wYmin, controllerCaps.wYmax );

      // NEGATE THIS SO IT MATCHES THE COORDINATE SYSTEM
      SendInputControllerAxisMessage( -Amount, kAxisY );
   }
   if ( ControllerStatus.dwZpos != previousControllerStatus.dwZpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwZpos,
                           controllerCaps.wZmin, controllerCaps.wZmax );

      SendInputControllerAxisMessage( Amount, kAxisZ );
   }
   if ( ControllerStatus.dwRpos != previousControllerStatus.dwRpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwRpos,
                           controllerCaps.wRmin, controllerCaps.wRmax );

      SendInputControllerAxisMessage( Amount, kAxisR );
   }
   if ( ControllerStatus.dwUpos != previousControllerStatus.dwUpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwUpos,
                           controllerCaps.wUmin, controllerCaps.wUmax );

      SendInputControllerAxisMessage( Amount, kAxisU );
   }
   if ( ControllerStatus.dwVpos != previousControllerStatus.dwVpos )
   {
      const float Amount = GetControllerAmount( ControllerStatus.dwVpos,
                           controllerCaps.wVmin, controllerCaps.wVmax );

      SendInputControllerAxisMessage( Amount, kAxisV );
   }

   if ( ControllerStatus.dwPOV != previousControllerStatus.dwPOV )
   {
      if ( ControllerStatus.dwPOV != JOY_POVCENTERED )
      {
         const float Radians = ( ( float )ControllerStatus.dwPOV / ( float )36000 ) * Tau();
         SendInputControllerAxisMessage( Radians, kPOV );
      }
      else
      {
         SendInputControllerAxisMessage( kPOVCentered, kPOV );
      }
   }
   previousControllerStatus = ControllerStatus;
}

void WindowsWindow::SendInputControllerButtonMessage( const eKeyState State, const uint32_t ButtonFlag )
{
   ControllerButtonMessage ToSend;
   ToSend.state = State;

   eControllerButton Button = kBtn1;
   for ( uint8_t ButtonIndex = 0; ButtonIndex < kMaxControllerButtons; ++ButtonIndex )
   {
      if ( ( 1 << ButtonIndex ) == ButtonFlag )
      {
         Button = ( eControllerButton )ButtonIndex;
         break;
      }
   }
   ToSend.which = Button;

   InputMessage Message;
   Message.type = kControllerButton;
   Message.controllerButton = ToSend;

   pf::gSendInputMessageCallback( Message );
}
void WindowsWindow::SendInputControllerAxisMessage( const float Amount, const eControllerAxis Which )
{
   ControllerAxisMessage ToSend;
   ToSend.value = Amount;
   ToSend.which = Which;

   InputMessage Message;
   Message.type = kControllerAxis;
   Message.controllerAxis = ToSend;

   pf::gSendInputMessageCallback( Message );
}


bool WindowsWindow::MouseInWindow( const int16_t PosX, const int16_t PosY )
{
   // Check if we're in the client rect
   RECT ClientRect;
   GetClientRect( windowHandle, &ClientRect );
   POINT MousePos;
   MousePos.x = PosX;
   MousePos.y = PosY;
   // If so:
   if ( PtInRect( &ClientRect, MousePos ) )
   {
      // Check if we're in another window
      ClientToScreen( windowHandle, &MousePos );

      // If so:
      if ( WindowFromPoint( MousePos ) != windowHandle )
      {
         // We should not hide the mouse
         return false;
      }
   }
   // If not in the client rect:
   else
   {
      // We should not hide the mouse
      return false;
   }

   return true;
}

bool WindowsWindow::CaptureController( const tControllerIndex Index )
{
   // We're only allowed to capture a new controller if we don't have one currently captured
   if ( capturedControllerIndex == kNull8 )
   {
      // Get information on the new controller
      joyGetDevCaps( Index, &controllerCaps, sizeof( controllerCaps ) );

      if ( joySetCaptureCalled == false )
      {
         // Capture all joysticks
         const tControllerIndex ControllerCount = joyGetNumDevs();
         for ( tControllerIndex CurControllerIndex = 0; CurControllerIndex < ControllerCount; ++CurControllerIndex )
         {
            joySetCapture( windowHandle, CurControllerIndex, 0, FALSE );
         }
         joySetCaptureCalled = true;
      }

      bool AxesAvailable[kMaxControllerAxes] = { false };
      if ( controllerCaps.wNumAxes >= 2 )
      {
         AxesAvailable[kAxis1] = true;
         AxesAvailable[kAxis2] = true;
      }
      AxesAvailable[kAxisZ] = controllerCaps.wCaps & JOYCAPS_HASZ;
      AxesAvailable[kAxisR] = controllerCaps.wCaps & JOYCAPS_HASR;
      AxesAvailable[kAxisU] = controllerCaps.wCaps & JOYCAPS_HASU;
      AxesAvailable[kAxisV] = controllerCaps.wCaps & JOYCAPS_HASV;
      AxesAvailable[kPOV] = controllerCaps.wCaps & JOYCAPS_HASPOV;

      pf::gControllerCapturedCallback( AxesAvailable );
      capturedControllerIndex = Index;
      return true;
   }
   return false;
}

bool WindowsWindow::ReleaseController()
{
   if ( capturedControllerIndex != kNull8 )
   {
      pf::gControllerReleasedCallback();
      capturedControllerIndex = kNull8;
      return true;
   }
   return false;
}

bool WindowsWindow::ControllerCaptured()
{
   return capturedControllerIndex != kNull8;
}

tControllerIndex WindowsWindow::GetControllerIndex()
{
   return capturedControllerIndex;
}
void WindowsWindow::ActivateContext()
{
   HDC DeviceContextHandle = GetDC( windowHandle );
   wglMakeCurrent( DeviceContextHandle, openGLContextHandle );
   ReleaseDC( windowHandle, DeviceContextHandle );
}
void WindowsWindow::DeactivateContext()
{
   HDC DeviceContextHandle = GetDC( windowHandle );
   wglMakeCurrent( DeviceContextHandle, NULL );
   ReleaseDC( windowHandle, DeviceContextHandle );
}
void WindowsWindow::SwapBuffers()
{
   HDC DeviceContextHandle = GetDC( windowHandle );
   ::SwapBuffers( DeviceContextHandle );
   ReleaseDC( windowHandle, DeviceContextHandle );
}

HWND WindowsWindow::GetHandle()
{
   return windowHandle;
}
tWindowID WindowsWindow::GetID()
{
   return id;
}
